package com.mjk.common.core.jdbc.ibatis;


import com.mjk.common.core.jdbc.datasource.DynamicDataSource;
import com.mjk.common.tools.logger.BizLogger;
import com.mjk.common.util.ObjectUtils;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.mapper.ClassPathMapperScanner;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.*;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;


@Configuration
@EnableTransactionManagement
@EnableConfigurationProperties({ DataSourceProperties.class, IbatisProperties.class })
public class IbatisAutoConfiguration {

    private static final Logger log = LoggerFactory.getLogger(IbatisAutoConfiguration.class);

    final
    IbatisProperties mybatisProperties;

    public IbatisAutoConfiguration(IbatisProperties mybatisProperties) {
        this.mybatisProperties = mybatisProperties;
    }

    @Bean
    
    public SqlSessionFactory sqlSessionFactory(DynamicDataSource dataSource, ApplicationContext context) throws IOException {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        Resource[] rs = null ;
        //设置事务 多数据源 在事务中可以切换
        //bean.setTransactionFactory(new MultiDataSourceTransactionFactory());
        ResourcePatternResolver rr = new PathMatchingResourcePatternResolver();
        if (mybatisProperties.getConfigLocation() != null && !mybatisProperties.getConfigLocation().trim().isEmpty()) {
            bean.setConfigLocation(rr.getResource(mybatisProperties.getConfigLocation()));
        }
        if (mybatisProperties.getMapperLocations() != null && !mybatisProperties.getMapperLocations().trim().isEmpty()) {
        	if(mybatisProperties.getMapperLocations().contains(",")) {
        		String[] rrs = mybatisProperties.getMapperLocations().split(",");
        		List<Resource> tmp = new ArrayList<>();
                for (String s : rrs) {
                    tmp.addAll(Arrays.asList(rr.getResources(s)));
                }
        		rs = tmp.toArray(new Resource[] {});
        		bean.setMapperLocations(rs);
        	}else {
        		rs = rr.getResources(mybatisProperties.getMapperLocations());
        		bean.setMapperLocations(rs);
        	}
        }
        if (mybatisProperties.getTypeAliasesPackage() != null && !mybatisProperties.getTypeAliasesPackage().trim().isEmpty()) {
            bean.setTypeAliasesPackage(mybatisProperties.getTypeAliasesPackage());
        }
        SqlSessionFactory sqlSessionFactory = null;
        try {
            sqlSessionFactory = bean.getObject();
            //启动mapper xml 热加载
            if(null != sqlSessionFactory) {
                new IbatisMapperRefresh(rs, sqlSessionFactory.getConfiguration());
                Map<String, Object> intercepts = context.getBeansWithAnnotation(Intercepts.class);

                for (Object interceptor : intercepts.values()) {
                    sqlSessionFactory.getConfiguration().addInterceptor((Interceptor) interceptor);
                }

            }
        } catch (Exception e) {
        	BizLogger.error(e);
            System.exit(0);
        }

        return sqlSessionFactory;
    }

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        ExecutorType executorType = this.mybatisProperties.getExecutorType();
        if (executorType != null) {
            return new SqlSessionTemplate(sqlSessionFactory, executorType);
        } else {
            return new SqlSessionTemplate(sqlSessionFactory);
        }
    }

    /**
     * This will just scan the same base package as Spring Boot does. If you want more power, you can explicitly use
     * {@link org.mybatis.spring.annotation.MapperScan} but this will get typed mappers working correctly, out-of-the-box, similar to using Spring
     * Data JPA repositories.
     */
    public static class AutoConfiguredMapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

        private ResourceLoader resourceLoader;

        private Environment environment;

        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            log.debug("Searching for mappers annotated with @Mapper @Intercepts");
            try {
                ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
                if (this.resourceLoader != null) {
                    scanner.setResourceLoader(this.resourceLoader);
                }
                scanner.setAnnotationClass(Mapper.class);
                scanner.registerFilters();
                scanner.doScan(getScanPackages());
                
                ClassPathBeanDefinitionScanner interceptsScanner = new ClassPathBeanDefinitionScanner(registry);
                interceptsScanner.setResourceLoader(this.resourceLoader);
                interceptsScanner.addIncludeFilter(new AnnotationTypeFilter(Intercepts.class));
                interceptsScanner.scan(getScanPackages());
            } catch (IllegalStateException ex) {
                log.debug("Could not determine auto-configuration " + "package, automatic mapper scanning disabled.");
            }
        }

        @Override
        public void setResourceLoader(ResourceLoader resourceLoader) {
            this.resourceLoader = resourceLoader;
        }

        @Override
        public void setEnvironment(Environment environment) {
            this.environment = environment;
        }

        private void addPackage(List<String> scanpackageList, String pack) {
            if (!scanpackageList.contains(pack)) {
                boolean ignore = false;
                for (String existPack : scanpackageList) {
                    if (pack.startsWith(existPack)) {
                        ignore = true;
                    } else if (existPack.startsWith(pack)) {
                        scanpackageList.remove(existPack);
                        break;
                    }
                }
                if (!ignore) {
                    scanpackageList.add(pack);
                }
            }
        }

        private String getCurrentPackage() {
            StackTraceElement[] emelents = Thread.currentThread().getStackTrace();
            String startClassName = emelents[emelents.length - 1].getClassName();
            return startClassName.substring(0, startClassName.lastIndexOf("."));
        }

        private String[] getScanPackages() {
            if (scanPackages == null) {
                String scanPackage = System.getProperty("bee.config.scanPackage");
                List<String> scanpackageList = new ArrayList<>();
                addPackage(scanpackageList, getCurrentPackage());
                if (!ObjectUtils.isEmpty(scanPackage)) {
                    for (String pack : scanPackage.split(",")) {
                        addPackage(scanpackageList, pack);
                    }
                }
                String springContextScanPackage = environment.getProperty("bee.config.scanPackage");
                if (null!= springContextScanPackage &&!ObjectUtils.isEmpty(springContextScanPackage)) {
                    for (String pack : springContextScanPackage.split(",")) {
                        addPackage(scanpackageList, pack);
                    }
                }
                scanPackages = new String[scanpackageList.size()];
                scanpackageList.toArray(scanPackages);
            }
            return scanPackages;
        }
        
        private String[] scanPackages;
    }

    /**
     * {@link org.mybatis.spring.annotation.MapperScan} ultimately ends up creating instances of {@link MapperFactoryBean}. If
     * {@link org.mybatis.spring.annotation.MapperScan} is used then this auto-configuration is not needed. If it is _not_ used, however, then this
     * will bring in a bean registrar and automatically register components based on the same component-scanning path as Spring Boot itself.
     */
    @Configuration
    @Import({ AutoConfiguredMapperScannerRegistrar.class })
    @ConditionalOnMissingBean(MapperFactoryBean.class)
    public static class MapperScannerRegistrarNotFoundConfiguration {

        @PostConstruct
        public void afterPropertiesSet() {
            BizLogger.info(MapperFactoryBean.class.getName()+"no fount");
        }
    }

    @Bean
    @ConditionalOnMissingBean
    public DataSourceTransactionManager transactionManager(DataSource dataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }

}
